/*============================================================================
  File:      pullcaseprocessor.h
 
  Summary:   Declaration of PULLCASEPROCESSOR class that is used to extract
			 data from Analysis Services in a 'pull' manner
 
  Date:		 June 30, 2004
------------------------------------------------------------------------------
  This file is part of the Microsoft SQL Server Code Samples.
 
  Copyright (C) 2003 Microsoft Corporation.  All rights reserved.
 
This source code is intended only as a supplement to Microsoft
Development Tools and/or on-line documentation.  See these other
materials for detailed information regarding Microsoft code samples.
 
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
============================================================================*/

#pragma once

#include "stdafx.h"
#include "..\..\dmalgo.h"
#include "DmhVector.h" // Using STL vectors




#include "caseprocessor.h"	// for CASEREADER


// IDMPushToPullCaseSetHelper
// This interface is designed to allow wrapping a 'push' DM case set interface
// as a 'pull' case set. It contains specific functions for retrieving 
// cases in a 'pull' manner,for initializing with the cache size, as well as 
// for multi-threaded synchronization
__interface ATL_NO_VTABLE __declspec(uuid("4D37387E-F7A1-46e6-B5F0-FE6CFABA4A18")) 
	IDMPushToPullCaseSetHelper : public IDMPullCaseSet
{
	HRESULT			Initialize	(IDMPushCaseSet*	pidmpushcaseset, DWORD	dwMaximumLimit);
	HRESULT			TerminateExecution(HRESULT hTerminationCode);
};



// PullCASE -- structure wrapping a single case
struct PullCASE
{
	
	DWORD		dwSize;				// size in bytes
	DM_CaseID	caseID;				// case ID
	ULONG		cAtts;				// Attributes in the case
	ULONG*		rgCase;				// The actual case
	PullCASE*	pNext;				// next PullCASE
	
	// General creation method
	static		HRESULT	CreatePullCASE(IDMMemoryAllocator*	pmalloc, PullCASE**	out_ppCase);
	// General deletion method
	static		HRESULT	DeletePullCASE(IDMMemoryAllocator*	pmalloc, PullCASE*	in_pCase);
};

// Class PullCASEQueue --  queue for holding the pushed cases cache
// It is not sinchronized, but its' owner must synchronize access to this queue
class PullCASEQueue
{
protected:
	PullCASE*						_pFirst;
	PullCASE*						_pLast;
	CComPtr<IDMMemoryAllocator>		_spidmmemoryallocator;
	DWORD							_dwTotalSize;
	DWORD							_dwMaxSize;

	HANDLE							_hEvtQueueHasData;	// event signaling that the queue has data
	HANDLE							_hEvtQueueHasRoom;	// event signaling that the queue has room for more data
public:
			PullCASEQueue();
			~PullCASEQueue();

			HRESULT					Initialize(IDMMemoryAllocator*	spidmmemoryallocator, DWORD dwMaxSize);
			HRESULT					Reset();

			// Both these methods return S_FALSE
			HRESULT					AddCase(PullCASE*	in_pCase);
			HRESULT					RemoveCase(PullCASE*&	out_rpCase);

	inline	bool					IsEmpty(){return _pFirst == NULL;}


	inline	HANDLE					QueueHasRoomEvent(){ return _hEvtQueueHasRoom; }
	inline	HANDLE					QueueHasDataEvent(){ return _hEvtQueueHasData; }

};



// PushToPullCaseProcessor exposes a 'pull' caseset on top of the 
// default 'push' one used by the DM Plug-in framework
// It builds a cache of cases of configurable size 
//  (a linked list of nodes, each containing one case)
// When the cache is full, it locks inside ProcessCase and waits for 
// some of the cases to be consumed
//
class ATL_NO_VTABLE PushToPullCaseProcessor: 
	public CComObjectRootEx<CComMultiThreadModel>,
	public IDMCaseProcessor,
	public IDMPushToPullCaseSetHelper,
	public DMHALLOC
{
  public:

	BEGIN_COM_MAP(PushToPullCaseProcessor)
		COM_INTERFACE_ENTRY(IDMCaseProcessor)
		COM_INTERFACE_ENTRY(IDMPushToPullCaseSetHelper)
	END_COM_MAP()

	PushToPullCaseProcessor();
	
	virtual			~PushToPullCaseProcessor();
	
	HRESULT			Reset	();

	// IDMCaseProcessor

	STDMETHOD						(RequestCapacity)(ULONG in_cCapacity);
	STDMETHOD						(ProcessCase)(DM_CaseID dmcaseid, ULONG cValue, ULONG* rgValue);

	// IDMPushToPullCaseSetHelper functions
	HRESULT							Initialize	(IDMPushCaseSet*	pidmpushcaseset, DWORD	dwMaximumLimit);
	STDMETHOD						(GetNextCase)(DM_CaseID* out_pdmcaseid, ULONG* out_pcValue, ULONG** out_prgValue);

	// Inherited from IDMPullCaseSet
	STDMETHOD						(RestartCases)();
	STDMETHOD						(IsDense)();
	
	
	// Termination method
	//	 Once this is called:
	//		if termination code is successful (end of cases), then:
	//			- ProcessCase will assert then return E_FAIL (cases are supposed to be completed)
	//			- GetNextCase will return S_FALSE when the cache becomes empty, signaling end of processing
	//		if termination code is not successful (error in processing cases, 
	//		inside the framework or inside the PULLCASEPROCESSOR) then
	//			- ProcessCase will return this termination code
	//			- GetNextCase will return this termination code
	HRESULT							TerminateExecution(HRESULT hTerminationCode);
  
protected:
	PullCASEQueue					_queueCache;
	CComCriticalSection				_lock;
	bool							_bDensePushSet;		// Is the source data dense?
	PullCASE*						_currentCase;
	

	bool							_bTerminated;
	HRESULT							_hTerminationCode;
	HANDLE							_hTermEvent;	// Termination event -- signaled when execution is terminated

	// Releases the current case memory
	void							ReleaseCurrentCase();
private:
};




// PULLCASEPROCESSOR is a sample case processor that implements a 'pull' mechanism 
// on top of the default DM Plug-in Push case processing interface
// A PULLCASEPROCESSOR object is to be declared on the stack
// PULLCASEPROCESSOR	pullCP;
// It is initialized	with the Initialize call
// The, it processes the cases with a ProcessCases call
// The following methods are of particular interest for this sample 
//
//	-- CaseProcessingProc -- a function used in a separate thread, that handles the 'push' 
//		case processing requirements of the plug-in
//	-- ProcessCase	--	performs the actual processing job, specific to the algorithm
//	
// The PULLCASEPROCESSOR model is compiled only if USE_PULL_CASESET is defined

	
class PULLCASEPROCESSOR : public DMHALLOC
{
public:
	PULLCASEPROCESSOR();
	virtual ~PULLCASEPROCESSOR();
	
	HRESULT	Initialize(	IDMAttributeSet*	pidmattributeset, 
						IDMPushCaseSet*	pidmpushcaseset,
						CASEREADER*		pcasereader,
						bool				bMissingAtRandom = false);
	
	HRESULT	ProcessCases(IDMContextServices *in_pExeContext);
	


	ULONG		_cAttribute;	// Number of attributes in the source data 
    
  protected:
	
	// Attributes

	CComPtr<IDMAttributeSet>	_spidmattributeset;

	// Actual case processing method
	HRESULT		ProcessCase(DM_CaseID dmcaseid, ULONG cValue, ULONG* rgValue);

	void		SetCaseThreadExitCode(HRESULT hRet);	

	static DWORD WINAPI	CaseProcessingProc(LPVOID lpParam);

	

  private:
	
	bool		_bMissingAtRandom;	// Convert state 0 of analysis-services
									// categorical values to be missing, and "shift"
									// the remaining states.
	VB			_vbCategorical;		// [iAttribute]

	bool		_bDensePushSet;		// Is the source data dense?

	CASEREADER*	_pcasereader;
	CASEREADER::VIEWTYPE	_viewtype;

	VDBL		_vdblValue;			// Used for both dense and sparse access
	VINT		_viAttribute;		// Used only for sparse access

	CComPtr<IDMPushCaseSet>					_spidmpushcaseset;		// The original 'push' case set
	CComPtr<IDMCaseProcessor>				_spidmcaseprocessor;	// case processor
	CComPtr<IDMPushToPullCaseSetHelper>		_spidmpullcaseset;		// the 'pull' case set
	BOOL									_bWorkingThreadTerminated;	// TRUE when the working thread is terminated
	HRESULT									_hrWorkingThreadResult;		// the result of the working thread
};


